package com.qibao.toolkit.exception.strategy.factory.impl;

import com.qibao.toolkit.exception.MissingAnnotationException;
import com.qibao.toolkit.exception.strategy.BaseExceptionHandleStrategyAware;
import com.qibao.toolkit.exception.strategy.ExceptionHandleStrategy;
import com.qibao.toolkit.exception.strategy.ExceptionHandleType;
import com.qibao.toolkit.exception.strategy.factory.ExceptionHandleStrategyHolder;
import com.qibao.toolkit.exception.strategy.impl.DefaultExceptionHandleStrategy;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 基于Spring管理bean的策略管理
 *
 * @author wangcheng
 * @version $Id: ExceptionHandleStrategyFactory.java, v0.1 2018/9/26 11:20 wangcheng Exp $$
 */
public class BaseSpringExceptionHandleStrategyHolder implements ExceptionHandleStrategyHolder, ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(BaseSpringExceptionHandleStrategyHolder.class);

    public static final String EXCEPTION_HANDLE_STRATEGY_HOLDER_BEAN_NAME = "baseSpringExceptionHandleStrategyHolder";

    private static final String DEFAULT_EXCEPTION_HANDLE_STRATEGY = "defaultExceptionHandleStrategy";

    private static final String BASE_STRATEGIES_PACKAGE = "com.qibao.toolkit.exception.strategy.impl";
    /**
     * 策略集合，key表示异常类型，value表示对key这种异常类型进行处理的策略
     */
    private Map<Class<? extends Throwable>, ExceptionHandleStrategy> strategyMap = Maps.newHashMap();

    private ExceptionHandleStrategy defaultStrategy;
    /**
     * 扫描到的要处理的异常集合
     */
    private Set<Class<? extends Throwable>> throwableTypes = Sets.newHashSet();
    /**
     * 扫描到的异常处理策略集合
     */
    private Set<ExceptionHandleStrategy> strategies = Sets.newHashSet();

    @Override
    public ExceptionHandleStrategy getStrategyByThrowable(Throwable throwable) {

        ExceptionHandleStrategy strategy = strategyMap.get(throwable.getClass());

        return strategy == null ? defaultStrategy : strategy;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        logger.info("开始加载异常处理策略对象");
        Map<String, ExceptionHandleStrategy> beansMap = applicationContext
                .getBeansOfType(ExceptionHandleStrategy.class);
        this.initData(beansMap);
        logger.info("加载策略成功，\n[{}]", strategyMapToString());

    }

    /**
     * 初始化数据
     */
    private void initData(Map<String, ExceptionHandleStrategy> strategyMap) {
        strategyMap.forEach(this::initThrowableAndStrategy);
        initStrategyMap();
    }

    /**
     * 初始化异常和策略信息
     */
    private void initThrowableAndStrategy(String name, ExceptionHandleStrategy strategy) {

        ExceptionHandleType handleType = strategy.getClass().getAnnotation(ExceptionHandleType.class);

        if (handleType == null) {
            throw new MissingAnnotationException("异常策略类必须有ExceptionHandleType注解");
        }
        //初始化异常类型
        Class<? extends Throwable>[] value = handleType.value();
        List<Class<? extends Throwable>> classes = Arrays.asList(value);
        throwableTypes.addAll(classes);

        strategies.add(strategy);

        if (DEFAULT_EXCEPTION_HANDLE_STRATEGY.equals(name) && strategy instanceof DefaultExceptionHandleStrategy) {

            defaultStrategy = strategy;

        }
    }

    /**
     * 初始化异常和策略映射
     */
    private void initStrategyMap() {

        throwableTypes.forEach(clz -> {
            ExceptionHandleStrategy strategy = findStrategyByThrowable(clz);
            strategyMap.put(clz, strategy);
        });

    }

    /**
     * 是否是基础策略
     */
    private Boolean isBaseStrategy(ExceptionHandleStrategy strategy) {
        //标记是否是基础异常处理策略
        String className = strategy.getClass().getName();
        int packageIdx = className.lastIndexOf(".");
        String packageString = className.substring(0, packageIdx);
        return BASE_STRATEGIES_PACKAGE.equals(packageString) && strategy instanceof BaseExceptionHandleStrategyAware;
    }

    /**
     * 通过异常类型获取策略
     */
    private ExceptionHandleStrategy findStrategyByThrowable(Class<? extends Throwable> throwableType) {
        List<ExceptionHandleStrategy> strategyList = Lists.newArrayList();
        strategies.forEach(strategy -> {
            ExceptionHandleType handleType = strategy.getClass().getAnnotation(ExceptionHandleType.class);
            Class<? extends Throwable>[] value = handleType.value();
            long count = Arrays.stream(value).filter(clz -> clz.equals(throwableType)).count();
            if (count > 0) {
                strategyList.add(strategy);
            }
        });
        if (CollectionUtils.isEmpty(strategyList)) {
            return defaultStrategy;
        }

        for (ExceptionHandleStrategy strategy : strategyList) {
            if (!isBaseStrategy(strategy)) {
                return strategy;
            }
        }

        return strategyList.get(0);
    }

    /**
     * 获取异常和策略的map信息
     */
    @Override
    public Map<Class<? extends Throwable>, ExceptionHandleStrategy> getStrategyMap() {
        return this.strategyMap;
    }

    /**
     * 获取扫描到的要处理的异常类型
     */
    @Override
    public Set<Class<? extends Throwable>> getThrowableTypes() {
        return throwableTypes;
    }

    /**
     * 获取扫描到的策略对象
     */
    @Override
    public Set<ExceptionHandleStrategy> getStrategies() {
        return strategies;
    }

    /**
     * 格式化
     */
    public String strategyMapToString() {
        StringBuffer stringBuffer = new StringBuffer();
        strategyMap.forEach((key, value) -> {
            stringBuffer.append("{");
            stringBuffer.append("\"throwable\":");
            stringBuffer.append(key.toString());
            stringBuffer.append(",");
            stringBuffer.append("\"strategy\":");
            stringBuffer.append(value.toString());
            stringBuffer.append("}\n");
        });
        return stringBuffer.toString();
    }

}
